<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vue3</title>
</head>
<body>
    <!-- 
        vue3的改动
            虚拟dom算法重写
            支持tree shaking （树摇）机制（es6推出的机制）: 主要是当在项目中引入其他模块时，会自动将用不到的代码，或者永远用不到的代码摇掉
            核心的响应式由Object.defineProperty改为Proxy
            更好的支持TS
            .....

        vue3相比vue2好在哪儿
            与vue2相比, vue3在捆绑包大小（通过树摇可轻41%）、初始渲染（快55%）、更新（快133%）和内存使用（少54%）等方面有了显著的性能改进

        vue3新特性
            新的生命周期函数钩子
            键盘事件不再支持keyCode    v-on:keyup.13不能用了
            组合式API
            新增内置组件
                <Transition>
                <TransitionGroup>
                <KeepAlive>
                <Teleport>
                <Suspense>
            data必须是一个函数
            .....
    -->
    <script>
        /*
            // 在 Vue3中，不再引入Vue实例了，引入createApp函数，这个函数可以创建app对象
            import { createApp } from 'vue'
            import App from './App.vue'
            // 这个app对象类似于Vue2当中的vm
            // app与vm的区别是：app更加轻量级。（app上的属性更少一些）
            const app = createApp(App)
            app.mount('#app')  // 挂载组件
            // unmount()  这个方法是卸载组件的
            // app.unmount('#app')
        */
    </script>
    <script>
        /*
            使用create-vue创建Vue3工程，是基于vite（快）的
            npm init vue@latest
                安装create-vue脚手架，同时创建vue3工程
                已经安装了就会直接创建工程


            vite(create-vue)创建的工程和webpack(vue-cli)创建的工程的不同
                - index.html放到public的外面
                    因为vite工程以index.html作为入口，不再使用main.js作为入口
                - vite工程中的配置文件变成了 vite.config.js , 对应之前的vue.confog.js
                - 启动，端口号不一样
        */
    </script>

    <!-- 
        回顾 Object.defineProperty实现响应式 存在什么问题？
                这种方式只能对修改（set）和读取（get）进行拦截
                所以当给对象新增一个属性或者删除对象的某个属性时，不会经过set和get，导致无法实现响应式
                并且通过数组下标去修改数组中的元素，也不会实现响应式（当元素是对象时除外）
        // Proxy原理
            Proxy是ES6新特性： window.Proxy对象
            通过Proxy可以创建一个代理对象
            语法规则：
                let proxyObj = new Proxy(targetObj, {
                    // 配置
                })
    -->
    <script>
        // Proxy实现原理
        // 目标对象
        let user = {
            name: 'jack'
        }
        // 通过Proxy来生成代理对象
        // 这里涉及到代理模式，代理模式的原理：通过代理对象去完成目标对象的任务，同时还可以额外新增一些程序
        let userProxy = new Proxy(user, {
            // 主要是通过这些配置项完成响应式处理的

            // 当读取属性的时候，get执行
            // 而且get方法是有参数的： tareget参数是代表目标对象, propertyName代表的是目标对象上的属性名,是一个字符串
            get(target, propertyName) {
                // console.log(typeof propertyName);
                // return target[propertyName];
                return Reflect.get(target, propertyName);    // 实际Vue3底层使用了反射机制Reflect对象
            },
            // 当修改某个属性的时候，或者新增某个个属性的时候，set执行
            // set方法的三个参数： target目标对象，propertyName属性名， value具体赋值
            set(target, propertyName, value) {
                // target[propertyName] = value
                Reflect.set(target, propertyName, value);    // 这里也是
            },
            // 当你删除某个属性的时候，deleteProperty执行
            // deleteProperty方法有两个参数： 第一个参数 目标对象 target,  第二个参数 属性名 propertyName 
            deleteProperty(target, propertyName) {
                // return delete target[propertyName]   // delete target[propertyName]删除成功了，要返回
                return Reflect.deleteProperty(target, propertyName)
            }
        })
    </script>
    <!-- 
        // setup是vue3当中新增的一个配置项
        // setup是一个函数
        import {ref} from 'vue'
        steup() {
            // setup函数的返回值
            // 1、返回一个对象，该对象的属性、方法等均可以在模板语法中使用，例如插值语法
            //      - return { name: '', age: 20 }
            // 2、返回一个渲染函数，从而执行渲染函数渲染页面
            //      - import {h} from 'vue'  引入渲染函数
            //      - return () => h('div', 'hello world')

            // setup中实现响应式，使用ref函数， 需要从 vue 中引入
            // let name = ref('jack')
            // 使用ref函数添加响应式之后，name变成了这样一个 对象  RefImpl {...}
            // RefImpl: Reference Implment 引用的实现
            // RefImpl是一个实例对象，称之为 引用对象
            // RefImpl这个引用对象中的 value 属性具有了响应式, 并且这个value底层使用Object.definProperty完成了响应式（原型上看）
            // RefImpl对象的value属性，有对应的set和get，当读取RefImpl对象的value属性时： get执行，当修改RefImpl对象的value属性时：set执行
            let nameRefImpl = ref('jack')
            console.log(nameRefImpl.vlaue)
            nameRefImpl.vlaue = 'rose'

            // 当 ref函数包裹的是一个对象
            let userRefImpl = ref({
                name: 'jack',
                age: 30
            })
            console.log(userRefImpl)
            // userRefImpl中也有value属性，这个value属性有set和get方法
            console.log(userRefImpl.value)
            // 但这个 value 属性是一个 Proxy 对象，代理对象
            // 所以访问具体属性时, 是这样访问
            console.log(userRefImpl.value.name)

            let schoolRefImpl = ref({
                name: '二中',
                addr: {
                    street: '北水河街'
                }
            })
            schoolRefImpl.value.addr.street = '南堤路'
            // Proxy实现的响应式，对象中的对象，对象中的对象，都有响应式处理（底层是递归的）

            总结：
                当ref包裹的是一个基本数据类型，响应式是如何实现的？ Object.defineProperty
                当ref包裹的是一个对象，响应式是如何实现的？ Object.defineProperty + Proxy 实现的
            return {
                nameRefImpl,
                schoolRefImpl
            }
        }
    -->

    <!-- 
        // ref通常适合于基本数据类型
        // 如果需要实现响应式的是一个对象，可以直接用 reactive 函数，用这个函数包裹的对象，直接就是一个Proxy
        // reactive函数不能用于基本数据类型
        import {reactive} from 'vue'
        steup() {
            // reactive函数，可以将一个对象直接包裹，实现响应式，底层是生成一个Proxy对象
            let userProxy = reactive({
                name: 'jack',
                age: 30
            })
            console.log(userProxy.name)

            return {
                userProxy
            }
        }
    -->

    <script>
        /*
            setup中怎么使用props?
            vue3调用setup函数之前，会给setup函数传递参数，第一个参数是： props
            props: ['name'],
            setup(props) {
                // props被包装成了一个代理对象，也是具有响应式的Proxy对象
                console.log(props)
            }
        */
    </script>

    <script>
        /*
            vue3的生命周期
                组合式API setup函数 在beforeCreate之前就执行了
            
                export default {
                    setup() {
                        // 安装组合式API
                        // 组合式API的生命周期函数钩子
                        // steup的执行时机在beforeCreted之前，所以这里就没有beforeCreat和created这两个周期了
                        onBeforeMount(() => {})
                        onMounted(() => {})
                        onBeforeUpdate(() => {})
                        onUpdate(() => {})
                        onBeforeUnmount(() => {})
                        onUnmouted(() => {})
                    }
                    beforeCreate() {
                        // 初始化 options API  选项式 api
                    }
                    created() {
                        // 选项式api初始化完成
                    },
                    beforeMount() {
                        // 挂载前
                    },
                    mounted() {
                        // 挂载
                    },
                    beforeUpdate() {
                        // 更新前
                    },
                    updated() {
                        // 更新完成
                    },
                    beforeUnmount() {
                        // 卸载前
                    },
                    unmounted() {
                        // 卸载
                    }
                }
        */
    </script>
    <script>
        /*
            vue3给组件绑定自定义事件
            <User @event1="alert(1)" />
            // user组件中触发event1事件
            setup(props, context) {  // context是上下文，其中有四个参数的，结构后有 { attrs, solts, emit, expose }
                context.emit('event1')
            }

            vue3中的全局事件总线
                需要借助第三方库 mitt
                npm i mitt
                封装event-bus
                    import mitt from 'mitt'
                    export default mitt()
                当引入event-bus时 ，会多一个emitter这个对象，这是一个全局事件总线对象
                使用
                    import emitter from '/utils/event-bus.js'
                    setup() {
                        onMounted(() => {
                            // 给全局事件总线绑定事件
                            emitter.on('event1', 事件函数(形参))
                        })
                    }
                    其他vue中触发
                    setup() {
                        emitter.emit('event1', 传参)

                        emitter.all.clear() // 清除所有全局事件总线绑定的事件

                        emitter.off('event1') // 解绑指定的事件
                    }
                    
        */
    </script>
    <script>
        /*
            vue3的计算属性
            setup() {

                let userName = ref('abc')
                // 这种写法是一种简写形式，使用了计算属性的get方法
                let reversedName = computed(() => {
                    return userName.value.split('').reverse().join()  // 反转名字
                })

                // 完整写法
                let reversedName2 = computed({
                    get() {
                        // 读取计算属性值的时候执行
                        return userName.value.split('').reverse().join()
                    },
                    set(val) {
                        // 修改计算属性的值的时候执行
                        // 修改这个值时让原来的userName值等于这个输入的值的反转
                        userName.value = val.split('').reverse().join()
                    }
                })
                return {
                    reversedName
                }
            }

            vue3的监听器
            // 被监听的数据需要具有响应式， 使用ref设置响应式参数
            let counter = ref(1)
            watch(counter, (newValue, oldValue) => {
                console.log(newValue, oldValue)
            }, {       // 配置项参数
                immediate: true,   // 默认是false
                deep: true          // 默认是false
            })
            // 具有相同业务的参数的监听，使用数组的方式,一次监视多个属性
            let num = ref(100)
            let num2 = ref(1)
            watch([num, num1], (newValue, oldValue) => {
                console.log(newValue, oldValue)
                // ...
            }, {
                immediate: true,
            })

            // 监听reactive数据
            let obj = reactive({
                counter: 1,
                a: {
                    b: {
                        c: {
                            num: 100
                        }
                    }
                }
            })
            watch(obj, (newValue, oldValue) => {
                // 当counter发生改变时，这两个值是一样的
                // 所以，对reactive函数获取的代理对象，在进行监听的时候，只能获取到newValue, oldValue无法获取
                console.log(newValue, oldValue)
                // 对于reactive函数读取的代理对象来说，默认是开启深度监听的，可以监听到num的变化
                // 并且这种深度监听是无法取消的
            }, {
                deep: false,   // 这样关闭不了深度监听, 设置无效
            })
            // 如果执行监听某个指定的属性，应该怎么办？
            // 只监听counter属性，注意，这时候，监听的第一个数据，必须是一个函数,结果返回这个属性
            watch(() => obj.counter, (newValue, oldValue) => {
                console.log(newValue, oldValue)
            })
            // 这里监听的属性可以写obj.a.b, 是因为 c 还是一个Proxy的代理对象，wacth监听器针对 Proxy代理的对象，都是只能获取到 newVlaue
            watch(obj.a.b, (newValue, oldValue) => {
                console.log(newValue, oldValue)      // 这里的newValue和oldValue是一样的
            })
            // 这里如果只监听num，需要写成一个函数，因为 num是一个基本数据类型的值, reactive中基本数据类型没有响应式的代理
            watch(()=>obj.a.b.c.num, (newValue, oldValue) => {
                console.log(newValue, oldValue)
            })
            // 数组形式
            watch([()=>obj.counter,()=>obj.a.b.c.num], (newValue, oldValue) => {
                console.log(newValue, oldValue)
            })
            // 还有一个细节
            watch(obj.a.b, (newValue, oldValue) => {
                console.log(newValue, oldValue)      // 这里的newValue和oldValue是一样的
            }, {
                deep: false          // 当监听的属性这样写 obj.a.b, 这里设置deep:false是无效的
            })
            watch(()=>obj.a.b, (newValue, oldValue) => {
                console.log(newValue, oldValue)      // 这里的newValue和oldValue是 不 一样的 !!!
            }, {
                deep: false          // 当监听的属性这样写 () => obj.a.b 的话, 这里设置deep:false是 有 效的 !!!
            })
        */
    </script>
    <script>
        /*
            当使用ref来包裹对象时，watch怎么监听？
            let counterRefImpl = ref(1)

            // 错误的，不能监听没有响应式的值
            // watch(counterRefImpl.value, (nv, ov) => {})

            // 可以的
            watch(()=>counterRefImpl.value, (nv, ov) => {})

            // 当是一个对象
            let data = ref({
                a: {
                    b: {
                        c: {
                            num: 1
                        }
                    }
                }
            })
            // 可以, 而且deep配置项是有效的， ov的值可以拿到，因为这个data不是一个Proxy对象，是一个RefImpl对象，默认是不开启深度监听的
            watch(data, (nv, ov) => {}, {deep: true})
            // 当ref包裹的是一个对象时，vue3底层会使用 Proxy 将这个对象变成响应式的，然后再用Object.defineProperty作代理
            // 所以 data.value 是一个Proxy对象，直接这样监听是可以的，不需要变成函数了
            // 这样跟监听 reactive 包裹的对象是一样的， oldValue拿不到， deep配置无效
            watch(data.value, (newValue, oldValue) => {}, {deep: false})
        
        */
    </script>
    <script>
        /*
            vue3的watchEffect API的使用
            let data = reactive({
                a: 1,
                b: 2,
                c: 3
            })
            // watchEffect函数的作用也是用来监视的，里面是一个回调函数
            // 这个回调函数什么时候执行？ 组件一加载会立刻执行一次
            watchEffect(() => {
                // 当这个回调函数使用到了 data.a ，data.b
                // 这个回调函数的执行时机是，只要 a 或者 b 发生变化，这个回调函数都会执行一次
                data.a = 100
                data.b = 10
            })
        */
    </script>
    <script>
        /*
            vue3的hook函数，为了代码复用，使用时勾出来， 相当于 vue2的minix
            hooks中hook.js
            export default function() {
                let a = ref(3.141592653)
                return a
            }
            使用
            import useHook from 'hook.js'
            setup(){
                let v = useHook()
                return v               // 这样在这个组件中的模板中可以直接使用 a 这个响应式的变量
            }
        */
    </script>
    <script>
        /*
            vue3的浅层次的响应式
                shallowRef和shallowReactive

                setup() {
                    //let counter = ref(1)
                    // shallowRef用在基本数据类型上，和ref没有区别
                    let counter = shallowRef(1)
                    // 当shallowRef用在一个对象上时，这个对象就不会有响应式了，不会有Proxy代理，只是一个Object对象
                    let data = shallowRef({
                        counter: 100
                    })
                    console.log(data.value)   // 这个是一个Object对象，这个value是有响应式的，而Object里的属性没有响应式了
                    // shallowRef的作用：有的时候，某个对象中的属性永远都不会改，要改的话，也是更换整个对象

                    // 使用了shallowReactive，这个对象第一层有响应式，里面再有对象就不支持响应式了， a对象、b对象都没有响应式
                    let obj = shallowReactive({
                        counter: 1000,
                        a: {
                            b: {
                                x: 10
                            }
                        }
                    })
                    return {
                        counter
                    }
                }
        */
    </script>
    <!-- 
        组合式API和选项式API的区别
            组合式API：
                组合式API + hook，关注点在：功能上
                一个hook一个功能

            选项式API：
                关注点在：一个一个选项上，配置项
                    选项式API关注点没有在功能上
                    没有独立功能的概念
                    当修改某个功能时，会比较乱，要去每一个配置项中找到并修改
    -->

    <script>
        /*
            vue3中的深只读和浅只读
                let data = reactive({
                    counter1: 111,
                    a: {
                        b: {
                            s: 115
                        }
                    }
                })
                // 深只读，要重新赋值（含义：具有响应式的对象中的所有属性，包括子对象中属性，它的所有值都是只读的不可修改的）
                data = readonly(data)
                // 浅只读： 具有响应式的对象的第一层属性值是只读的
                data = shallowReadonly(data)
                // 作用：
                // 当其他组件传递了一个响应式的对象数据，但是要求不能修改这个响应式对象的数据
        */
    </script>
    <script>
        /*
            判断数据是否有响应式的四个函数
                isRef
                    检查某个值是否是ref
                isReactive
                    检查一个对象是否是由 reactive() 或者 shallowReactive() 创建的代理
                isProxy
                    检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理
                isReadonly
                    检查传入的值是否是只读对象
        */
    </script>
    <script>
        /*
            toRef和toRefs

            setup() {
                let data = reactive({
                    counter: 1,
                    a: {
                        b: {
                            sum: 50
                        }
                    }
                })

                return {
                    // toRef函数的使用
                    counter1: toRef(data, 'counter'),   // 将某个响应式对象的某个属性变成响应式
                    sum: toRef(data.a.b, 'sum')
                }

                // toRef的语法格式： toRef(对象，'该对象中的属性名')
                // toRef函数执行后会生成一个全新的对象：ObjectRefImpl(引用对象)
                // 只要有引用对象，就有value属性，value属性是响应式的
                // toRef相当于变向的代理，二次代理，value有get和set，当访问counter1时，底层会get data对象的 counter属性

                // toRef的缺点是要写多次，toRefs来解决这个问题
                // toRefs的语法格式： toRefs(对象)
                // toRefs函数会生成一个对象，这个对象的键值对是这样的 { counter: ObjectRefImpl, a: ObjectRefImpl }
                //      属性键是 data对象的第一层的各个属性，属性值也是对应的 生成 ObjectRefImpl引用对象
                return {
                    // 使用
                    ...toRefs(data)
                }
            }
        */
    </script>
    <script>
        /*
            转换为原始：toRaw
                将响应式对象转换为普通对象，只适用于reactive生成的响应式对象
                应用：有时处理业务的时候，处理到某个环节，希望修改的某个数据后取消响应式
            标记为原始：markRaw
                标记某个对象，让这个对象永远不具备响应式
                应用：当集成一些第三方库的时候，当有一个巨大的只读列表，不让其具备响应式式一种性能优化
        */
    </script>
    <script>
        /*
            vue3内置组件之 Fragment组件
                是用作最后的根标签包裹所有元素的
            vue3内置组件之 Teleport组件
                翻译为远距离传送
        */
    </script>
    <script>
        /*
            vue3的隔代数据传递
                provide/inject
                适用于：爷爷组件向后代组件传值的情况
        */
    </script>
    <script>
        /*
            自定义ref：customRef
            使用自定义的ref同样具有响应式
                setup() {
                    // 使用内置ref
                    // let name = ref('tet')
                    // 创建一个防抖的 ref
                    function useDebouncedRef(value) {
                        let t;
                        // 自定义的ref这个函数体当中的代码不能随便写，必须符合ref规范
                        // 通过调用customRef函数来创建一个自定义的ref对象，然后返回出去
                        // 调用customRef函数的时候必须给该函数传一个回调，这个回调可以叫做 factory ，一个工厂
                        // 对于这个回调函数来说，有两个非常重要的参数：track是追踪， trigger是触发
                        return customRef((track, trigger) => {
                            // 对于这个factory回调来说，必须要返回一个对象，并且对象要要有get
                            return {
                                // 模板语句中只要用到该数据，get就会自动调用
                                get() {
                                    console.log('get')
                                    track()             // 追踪value的变化
                                    return value
                                },
                                // 当模板中修改该数据的时候，set会自动调用
                                set(newValue) {
                                    console.log('set')
                                    clearTimeout(t)          // 防抖
                                    t=setTimeout(() => {     // 延迟一秒执行
                                        value = newValue  
                                        trigger()       
                                    }, 1000)
                                    // value = newValue        // 改变值
                                    // trigger()          // 触发，通知去调用get方法
                                }
                            }
                        })
                    }

                    // 使用自定义的ref
                    let name = useDebouncedRef('tet')

                }
        */
    </script>
</body>
</html>